package com.emergency.embackend.service.impl;

import com.emergency.embackend.entity.CollectOperate;
import com.emergency.embackend.entity.LikeOperate;
import com.emergency.embackend.entity.User;
import com.emergency.embackend.exception.EmException;
import com.emergency.embackend.mapper.CollectOperateMapper;
import com.emergency.embackend.mapper.LikeOperateMapper;
import com.emergency.embackend.mapper.OperateMapper;
import com.emergency.embackend.param.VideoOperate;
import com.emergency.embackend.service.OperateService;
import com.emergency.embackend.utils.IpUtils;
import com.emergency.embackend.utils.RedisConstants;
import com.emergency.embackend.utils.UserHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author chddd
 * @description
 * @create 2024/2/28 22:43:14
 */
@Service
@Slf4j
public class OperateServiceImpl implements OperateService {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private OperateMapper operateMapper;
    @Autowired
    private LikeOperateMapper likeOperateMapper;
    @Autowired
    private CollectOperateMapper collectOperateMapper;

    //这个作为喜欢的队列,点赞只有视频，文章，分享有
    private BlockingQueue<LikeOperate> likeDataTasks = new ArrayBlockingQueue<>(1024 * 1024);
    //这个作为收藏得队列
    private BlockingQueue<CollectOperate> collectDataTasks = new ArrayBlockingQueue<>(1024 * 1024);
    private static final ExecutorService LIKE_OPERATE_EXECUTOR = Executors.newSingleThreadExecutor();
    private static final ExecutorService COLLECT_OPERATE_EXECUTOR = Executors.newSingleThreadExecutor();

    /**
     * 这个方法的作用是保证在这个类初始化的后，就可以开始执行子线程的调用
     * 因为要一开始就要判断队列中是否有信息可用，可用就执行
     */
    @PostConstruct
    private void init() {
        LIKE_OPERATE_EXECUTOR.submit(new LikeOperateHandler());
        COLLECT_OPERATE_EXECUTOR.submit(new CollectOperateHandler());
    }

    private OperateService proxy;

    @Override
    public Integer like(Long id, char type) {
        String isLikeKey = null;
        String likeCountKey = null;
        switch (type) {
            case '0':
                //视频
                isLikeKey = RedisConstants.VIDEO_LIKE_USER + id;
                likeCountKey = RedisConstants.VIDEO_LIKE_COUNT + id;
                break;
            case '1':
                isLikeKey = RedisConstants.ARTICLE_LIKE_USER + id;
                likeCountKey = RedisConstants.ARTICLE_LIKE_COUNT + id;
                break;
            case '2':
                isLikeKey = RedisConstants.SHARE_LIKE_USER + id;
                likeCountKey = RedisConstants.SHARE_LIKE_COUNT + id;
                break;
            default:
                break;
        }
        Long userId = UserHolder.getUser().getId();
        Boolean isLike = stringRedisTemplate.opsForSet().isMember(isLikeKey, String.valueOf(userId));
        //获取点赞数
        String s = stringRedisTemplate.opsForValue().get(likeCountKey);
        Integer likeCount = null;
        //生成对象
        LikeOperate likeOperate = createLikeOperate(id, userId, type);
        //已经点赞
        proxy = (OperateService) AopContext.currentProxy();
        if (isLike) {
            likeCount = Integer.valueOf(s);
            //总的点赞数-1
            stringRedisTemplate.opsForValue().set(likeCountKey, String.valueOf(likeCount - 1));
            //挪出set列表
            stringRedisTemplate.opsForSet().remove(isLikeKey, String.valueOf(userId));
            likeOperate.setIsLike(String.valueOf(0));
            likeDataTasks.add(likeOperate);
            //返回0表示 要取消点赞
            return Integer.valueOf(0);
        }
        //没有点赞
        //总的点赞数+1
        if(s!=null) {
            likeCount = Integer.valueOf(s);
            stringRedisTemplate.opsForValue().set(likeCountKey, String.valueOf(likeCount + 1));
        }else{
            stringRedisTemplate.opsForValue().set(likeCountKey,"1");
        }
        //加入set列表
        stringRedisTemplate.opsForSet().add(isLikeKey, String.valueOf(userId));
        likeOperate.setIsLike(String.valueOf(1));
        //返回1表示点赞成功
        //异步执行对于数据库的操作
        likeDataTasks.add(likeOperate);
        return Integer.valueOf(1);
    }

    private LikeOperate createLikeOperate(Long id, Long userId, char type) {
        LikeOperate likeOperate = new LikeOperate();
        likeOperate.setDataId(id);
        likeOperate.setUserId(userId);
        likeOperate.setDataType(String.valueOf(type));
        return likeOperate;
    }

    @Override
    public Integer collect(Long id, char type) {
        String isCollectKey = null;
        String collectCountKey = null;
        switch (type) {
            case '0':
                //视频
                isCollectKey = RedisConstants.VIDEO_COLLECT_USER + id;
                collectCountKey = RedisConstants.VIDEO_COLLECT_COUNT + id;
                break;
            case '1':
                isCollectKey = RedisConstants.ARTICLE_COLLECT_USER + id;
                collectCountKey = RedisConstants.ARTICLE_COLLECT_COUNT + id;
                break;
            default:
                break;
        }
        Long userId = UserHolder.getUser().getId();
        Boolean isCollect = stringRedisTemplate.opsForSet().isMember(isCollectKey, userId.toString());
        //获取收藏数
        String s = stringRedisTemplate.opsForValue().get(collectCountKey);
        Integer collectCount;
        //创建对象
        CollectOperate collectOperate = createCollectOperate(id, userId, type);
        proxy = (OperateService) AopContext.currentProxy();
        //已经收藏
        if (isCollect) {
            collectCount = Integer.valueOf(s);
            //总的收藏数-1
            stringRedisTemplate.opsForValue().set(collectCountKey, String.valueOf(collectCount - 1));
            //挪出set列表
            stringRedisTemplate.opsForSet().remove(isCollectKey, userId.toString());
            collectOperate.setIsCollect(String.valueOf(0));
            collectDataTasks.add(collectOperate);
            //返回0表示 要取消收藏
            return Integer.valueOf(0);
        }
        //没有收藏
        //总的收藏数+1
        if(s!=null) {
            collectCount = Integer.valueOf(s);
            stringRedisTemplate.opsForValue().set(collectCountKey, String.valueOf(collectCount + 1));
        }else{
            stringRedisTemplate.opsForValue().set(collectCountKey,"1");
        }
        //加入set列表
        stringRedisTemplate.opsForSet().add(isCollectKey, String.valueOf(userId));
        collectOperate.setIsCollect(String.valueOf(1));
        collectDataTasks.add(collectOperate);
        //返回1表示收藏成功
        return Integer.valueOf(1);

    }

    private CollectOperate createCollectOperate(Long id, Long userId, char type) {
        CollectOperate collectOperate = new CollectOperate();
        collectOperate.setDataId(id);
        collectOperate.setUserId(userId);
        collectOperate.setDataType(String.valueOf(type));
        return collectOperate;
    }

    private class LikeOperateHandler implements Runnable {
        @Override
        public void run() {
            //子线程，开启线程任务，不断从阻塞队列中获取信息，实现异步下单功能
            while (true) {
                //1.获取队列中的订单信息
                try {
                    //从阻塞队列中获取
                    LikeOperate likeOperate = likeDataTasks.take();
                    //2.操作数据库
                    proxy.handlerLikeData(likeOperate);
                } catch (Exception e) {
                    log.error("数据异常", e);
                }
            }
        }
    }

    private class CollectOperateHandler implements Runnable {
        @Override
        public void run() {
            //子线程，开启线程任务，不断从阻塞队列中获取信息，实现异步下单功能
            while (true) {
                //1.获取队列中的订单信息
                try {
                    //从阻塞队列中获取
                    CollectOperate collectOperate = collectDataTasks.take();
                    //2.操作数据库
                    proxy.handlerCollectData(collectOperate);
                } catch (Exception e) {
                    log.error("数据异常", e);
                }
            }
        }
    }

    @Override
    @Transactional
    public void handlerLikeData(LikeOperate likeOperate) {
        //如果取消点赞，则删除该条数据
        if ("0".equals(likeOperate.getIsLike())) {
            //删除数据
            likeOperateMapper.delete(likeOperate);
        } else {
            //新增数据
            likeOperateMapper.insert(likeOperate);
        }
    }

    @Override
    @Transactional
    public void handlerCollectData(CollectOperate collectOperate) {
        if ("0".equals(collectOperate.getIsCollect())) {
            collectOperateMapper.delete(collectOperate);
        } else {
            collectOperateMapper.insert(collectOperate);
        }
    }

    @Override
    public void addViews(Long id, Long userId, char status) {
        //存储浏览量的key
        String viewKey = null;
        //用于判断是否看过的key
        String repeatViewKey = null;
        //用于存储用户记录的key
        String historyKey = null;

        switch (status) {
            case '0':
                viewKey = RedisConstants.VIEW_VIDEO_COUNT_KEY + id;
                repeatViewKey = RedisConstants.VIEW_VIDEO_REPEAT_KEY + id;
                historyKey = RedisConstants.VIEW_VIDEO_HISTORY_KEY + userId;
                break;
            case '1':
                viewKey = RedisConstants.VIEW_ARTICLE_COUNT_KEY + id;
                repeatViewKey = RedisConstants.VIEW_ARTICLE_REPEAT_KEY + id;
                historyKey = RedisConstants.VIEW_ARTICLE_HISTORY_KEY + userId;
                break;
            case '2':
                viewKey = RedisConstants.VIEW_SHARE_COUNT_KEY + id;
                repeatViewKey = RedisConstants.VIEW_SHARE_REPEAT_KEY + id;
                historyKey = RedisConstants.VIEW_SHARE_HISTORY_KEY + userId;
                break;
            case '3':
                viewKey = RedisConstants.VIEW_HELP_COUNT_KEY + id;
                repeatViewKey = RedisConstants.VIEW_HELP_REPEAT_KEY + id;
                historyKey = RedisConstants.VIEW_HELP_HISTORY_KEY + userId;
                break;
            default:
                break;
        }
        //获取当前时间戳
        double currentTime = System.currentTimeMillis();
        //获取请求信息
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
        HttpServletRequest request = servletRequestAttributes.getRequest();
        //获取ip地址
        String ip = IpUtils.getIpAddr(request);
        //判断用户是否浏览过
        Boolean flag = stringRedisTemplate.opsForSet().isMember(repeatViewKey, ip);
        //获取该视频的浏览量
        String views = stringRedisTemplate.opsForValue().get(viewKey);
        Long v = 1L;
        //如果浏览量不为0
        if (views != null) {
            v = Long.valueOf(views);
            //如果不存在该ip地址则要加浏览量要加1（没看过）避免未登陆的用户无法记录其值
            if (!flag) {
                stringRedisTemplate.opsForSet().add(repeatViewKey, ip);
                v = v + 1;
            }
            //如果浏览量为0，那么代表一定没看过
        } else {
            stringRedisTemplate.opsForSet().add(repeatViewKey, ip);
        }
        //无论是什么情况都要,如果存在就更新时间，不存在则新增，zSet实现
        if (userId != null) {
            stringRedisTemplate.opsForZSet().add(historyKey, id.toString(), currentTime);
        }
        stringRedisTemplate.opsForValue().set(viewKey, v.toString());
    }

    //获取对应的点赞量和收藏量
    @Override
    public Long getLikeOrCollectCount(String key) {
        if (key != null && Boolean.TRUE.equals(stringRedisTemplate.hasKey(key))) {
            if (stringRedisTemplate.opsForValue().get(key) != null) {
                return Long.valueOf(stringRedisTemplate.opsForValue().get(key));
            }
        }
        return 0L;
    }

}
